package com.example.meetsmart.service;

import com.example.meetsmart.model.Booking;
import com.example.meetsmart.model.Booking.BookingStatus;
import com.example.meetsmart.model.Room;
import com.example.meetsmart.model.User;
import com.example.meetsmart.repository.BookingRepository;
import com.example.meetsmart.repository.RoomRepository;
import com.example.meetsmart.repository.UserRepository;
import com.example.meetsmart.exception.BookingConflictException;
import com.example.meetsmart.exception.ResourceNotFoundException;
import com.example.meetsmart.exception.InvalidBookingTimeException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Pageable;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.List;

@Service
public class BookingService {
    private final BookingRepository bookingRepository;
    private final RoomRepository roomRepository;
    private final UserRepository userRepository;
    private final JavaMailSender mailSender;

    @Value("${meetsmart.booking.max-duration-hours}")
    private int maxDurationHours;

    @Value("${meetsmart.booking.min-advance-booking-days}")
    private int minAdvanceBookingDays;

    @Value("${meetsmart.booking.max-advance-booking-days}")
    private int maxAdvanceBookingDays;

    @Value("${meetsmart.booking.reminder-minutes-before}")
    private int reminderMinutesBefore;

    public BookingService(BookingRepository bookingRepository, RoomRepository roomRepository,
                          UserRepository userRepository, JavaMailSender mailSender) {
        this.bookingRepository = bookingRepository;
        this.roomRepository = roomRepository;
        this.userRepository = userRepository;
        this.mailSender = mailSender;
    }

    @Transactional(readOnly = true)
    public List<Booking> getAllBookings(Pageable pageable) {
        return bookingRepository.findAll();
    }

    @Transactional(readOnly = true)
    public Booking getBookingById(Long id) {
        return bookingRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("Booking not found with id: " + id));
    }

    @Transactional(readOnly = true)
    public List<Booking> getBookingsByUser(Long userId, Pageable pageable) {
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new ResourceNotFoundException("User not found with id: " + userId));
        return bookingRepository.findByUser(user);
    }

    @Transactional(readOnly = true)
    public List<Booking> getBookingsByRoom(Long roomId, Pageable pageable) {
        Room room = roomRepository.findById(roomId)
                .orElseThrow(() -> new ResourceNotFoundException("Room not found with id: " + roomId));
        return bookingRepository.findByRoom(room);
    }

    @Transactional(readOnly = true)
    public List<Booking> getBookingsByStatus(BookingStatus status, Pageable pageable) {
        return bookingRepository.findByStatus(status);
    }

    @Transactional
    public Booking createBooking(Booking booking, Long userId, Long roomId) {
        validateBookingTime(booking.getStartTime(), booking.getEndTime());

        User user = userRepository.findById(userId)
                .orElseThrow(() -> new ResourceNotFoundException("User not found with id: " + userId));
        Room room = roomRepository.findById(roomId)
                .orElseThrow(() -> new ResourceNotFoundException("Room not found with id: " + roomId));

        booking.setUser(user);
        booking.setRoom(room);
        booking.setStatus(BookingStatus.PENDING);

        checkForConflicts(booking);

        Booking savedBooking = bookingRepository.save(booking);
        sendBookingConfirmationEmail(savedBooking);
        return savedBooking;
    }

    @Transactional
    public Booking updateBookingStatus(Long id, BookingStatus status, String rejectionReason) {
        Booking booking = bookingRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("Booking not found with id: " + id));

        if (booking.getStatus() == BookingStatus.CANCELLED || booking.getStatus() == BookingStatus.COMPLETED) {
            throw new IllegalStateException("Cannot change status of a cancelled or completed booking");
        }

        booking.setStatus(status);
        if (status == BookingStatus.REJECTED && rejectionReason != null) {
            booking.setDescription(booking.getDescription() + "\nRejection Reason: " + rejectionReason);
        }

        Booking updatedBooking = bookingRepository.save(booking);
        sendStatusUpdateEmail(updatedBooking);
        return updatedBooking;
    }

    @Transactional
    public Booking cancelBooking(Long id, Long userId) {
        Booking booking = bookingRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("Booking not found with id: " + id));

        if (!booking.getUser().getId().equals(userId)) {
            throw new SecurityException("User is not authorized to cancel this booking");
        }

        if (booking.getStatus() == BookingStatus.CANCELLED) {
            throw new IllegalStateException("Booking is already cancelled");
        }

        if (booking.getStatus() == BookingStatus.COMPLETED) {
            throw new IllegalStateException("Cannot cancel a completed booking");
        }

        booking.setStatus(BookingStatus.CANCELLED);
        Booking cancelledBooking = bookingRepository.save(booking);
        sendCancellationEmail(cancelledBooking);
        return cancelledBooking;
    }

    @Transactional(readOnly = true)
    public List<Booking> findCurrentBookings() {
        return bookingRepository.findCurrentBookings(LocalDateTime.now());
    }

    @Transactional(readOnly = true)
    public List<Booking> findBookingsBetween(LocalDateTime start, LocalDateTime end, Pageable pageable) {
        return bookingRepository.findBookingsBetween(start, end);
    }

    private void validateBookingTime(LocalDateTime startTime, LocalDateTime endTime) {
        LocalDateTime now = LocalDateTime.now();

        // Check if start time is before end time
        if (startTime.isAfter(endTime) || startTime.isEqual(endTime)) {
            throw new InvalidBookingTimeException("Start time must be before end time");
        }

        // Check maximum duration
        long durationHours = Duration.between(startTime, endTime).toHours();
        if (durationHours > maxDurationHours) {
            throw new InvalidBookingTimeException("Booking duration cannot exceed " + maxDurationHours + " hours");
        }

        // Check minimum advance booking
        long daysInAdvance = Duration.between(now.toLocalDate().atStartOfDay(), startTime.toLocalDate().atStartOfDay()).toDays();
        if (daysInAdvance < minAdvanceBookingDays) {
            throw new InvalidBookingTimeException("Bookings must be made at least " + minAdvanceBookingDays + " days in advance");
        }

        // Check maximum advance booking
        if (daysInAdvance > maxAdvanceBookingDays) {
            throw new InvalidBookingTimeException("Bookings cannot be made more than " + maxAdvanceBookingDays + " days in advance");
        }
    }

    private void checkForConflicts(Booking booking) {
        List<Booking> conflicts = bookingRepository.findConflictingBookings(
                booking.getRoom(),
                booking.getStartTime(),
                booking.getEndTime());

        if (!conflicts.isEmpty()) {
            throw new BookingConflictException("The room is already booked for the selected time slot");
        }
    }

    private void sendBookingConfirmationEmail(Booking booking) {
        SimpleMailMessage message = new SimpleMailMessage();
        message.setTo(booking.getUser().getEmail());
        message.setSubject("Booking Confirmation: " + booking.getTitle());
        message.setText(String.format(
                "Dear %s,\n\n" +
                        "Your booking has been received:\n" +
                        "Room: %s\n" +
                        "Time: %s to %s\n" +
                        "Status: %s\n\n" +
                        "You will receive another email once your booking is approved or rejected.\n\n" +
                        "Best regards,\nMeetSmart Team",
                booking.getUser().getUsername(),
                booking.getRoom().getName(),
                booking.getStartTime(),
                booking.getEndTime(),
                booking.getStatus()));

        mailSender.send(message);
    }

    private void sendStatusUpdateEmail(Booking booking) {
        SimpleMailMessage message = new SimpleMailMessage();
        message.setTo(booking.getUser().getEmail());
        message.setSubject("Booking Status Update: " + booking.getTitle());

        String statusMessage = booking.getStatus() == BookingStatus.APPROVED ?
                "Your booking has been approved." :
                "Your booking has been rejected.";

        message.setText(String.format(
                "Dear %s,\n\n" +
                        "The status of your booking has been updated:\n" +
                        "Room: %s\n" +
                        "Time: %s to %s\n" +
                        "New Status: %s\n\n" +
                        "%s\n\n" +
                        "Best regards,\nMeetSmart Team",
                booking.getUser().getUsername(),
                booking.getRoom().getName(),
                booking.getStartTime(),
                booking.getEndTime(),
                booking.getStatus(),
                statusMessage));

        mailSender.send(message);
    }

    private void sendCancellationEmail(Booking booking) {
        SimpleMailMessage message = new SimpleMailMessage();
        message.setTo(booking.getUser().getEmail());
        message.setSubject("Booking Cancelled: " + booking.getTitle());
        message.setText(String.format(
                "Dear %s,\n\n" +
                        "Your booking has been cancelled:\n" +
                        "Room: %s\n" +
                        "Time: %s to %s\n\n" +
                        "Best regards,\nMeetSmart Team",
                booking.getUser().getUsername(),
                booking.getRoom().getName(),
                booking.getStartTime(),
                booking.getEndTime()));

        mailSender.send(message);
    }

    @Scheduled(fixedRate = 60000) // Run every minute
    public void sendReminderEmails() {
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime reminderTime = now.plusMinutes(reminderMinutesBefore);

        List<Booking> upcomingBookings = bookingRepository.findBookingsBetween(
                now,
                reminderTime);

        upcomingBookings.stream()
                .filter(b -> b.getStatus() == BookingStatus.APPROVED)
                .forEach(this::sendReminderEmail);
    }

    private void sendReminderEmail(Booking booking) {
        SimpleMailMessage message = new SimpleMailMessage();
        message.setTo(booking.getUser().getEmail());
        message.setSubject("Reminder: Upcoming Booking - " + booking.getTitle());
        message.setText(String.format(
                "Dear %s,\n\n" +
                        "This is a reminder for your upcoming booking:\n" +
                        "Room: %s\n" +
                        "Time: %s to %s\n\n" +
                        "The booking will start in %d minutes.\n\n" +
                        "Best regards,\nMeetSmart Team",
                booking.getUser().getUsername(),
                booking.getRoom().getName(),
                booking.getStartTime(),
                booking.getEndTime(),
                reminderMinutesBefore));

        mailSender.send(message);
    }
}